﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Ivory.Libs; 

namespace Ivory
{
    public sealed class ScreenManager : GameComponent 
    {
        private enum ManagerScreenState {
            Splashes = 0,
            Menu = 1,
            Game = 2,
        };

        //Fields
        private ManagerScreenState mScreenState = ManagerScreenState.Splashes; //Splashes by default
        public Config Config;

        private List<SplashScreenBase> splashScreens;
        private int splashCounter = 0;

        private Dictionary<string, GameScreen> gameScreens;
        private Dictionary<string, MenuScreen> menuScreens;
        private string screenKeyHolder = null;
        private string mainMenuHolder = null;
                
        private GraphicsDeviceManager gdm;
        private SpriteBatch BatchDevice;

		private IvoryLog _ManagerLog; 
		public IvoryLog ManagerLog
		{
			get { return _ManagerLog; }
			set { _ManagerLog = value; }
		} 		
        
        //Construct
        public ScreenManager(Game game, string configPath)
            : base(game)
        {            
            Config = new Config(game.Content.RootDirectory + "/" + configPath);
            setupFromConfig();

            splashScreens = new List<SplashScreenBase>();
            gameScreens = new Dictionary<string, GameScreen>();
            menuScreens = new Dictionary<string, MenuScreen>();

            mScreenState = ManagerScreenState.Splashes;
            splashCounter = 0;

            screenKeyHolder = null;

            _ManagerLog = new IvoryLog(game.Content.RootDirectory + "\\managerslog.log");
            _ManagerLog.ShowTimeStamp = true;
            _ManagerLog.Write("Manager Started");

            //Register an event to tell us when we're exiting
            Game.Exiting += new EventHandler<EventArgs>(OnGameExiting);
        }
        
        //Methods
        #region @@## Screen Mutators ##@@

        public bool AddSplashScreen(SplashScreen ss) {
            
            if(splashScreens.Contains(ss))
                throw new Exception("Firefly Error #002 : SplashScreen with the same key already added");

            ss.Manager = this;
            splashScreens.Add(ss);
            return true;
        }
        public bool AddGameScreen(string Key, GameScreen screen) {
            if (gameScreens.ContainsKey(Key))
                throw new Exception("Firefly Error #002 : GameScreen with the same key already added");

            screen.Manager = this;
            gameScreens.Add(Key, screen);
            return true;
        }
        public bool AddMenuScreen(string Key, MenuScreen screen, bool mainmenu) {
            if (menuScreens.ContainsKey(Key))
                throw new Exception("Firefly Error #002 : MenusScreen with the same key already added");

            if (menuScreens.Count == 0 || mainmenu)
            {
                mainMenuHolder = Key;
                screen.isMainMenu = true;
            }
            else
                screen.isMainMenu = false;

            screen.Manager = this; //assign manager
            menuScreens.Add(Key, screen);
            return true;
        }

        private void SwitchToo(string key, ManagerScreenState state)
        {
            //1/ unload the previous screen
            if (mScreenState == ManagerScreenState.Game)
                gameScreens[screenKeyHolder].unload();

            //2/ change the key
            screenKeyHolder = key;
            mScreenState = state;
        }
        public bool SwitchToGameScreen(string Key)
        {
            if (!gameScreens.ContainsKey(Key))
                throw new Exception("Firefly Error #003 : Couldnt not find the GameScreen key='" + Key + "'");

            SwitchToo(Key, ManagerScreenState.Game);
            return true;
        }
        public bool SwitchToMenuScreen(string Key)
        {
            if (!menuScreens.ContainsKey(Key))
                throw new Exception("Firefly Error #003 : Couldnt not find the MenuScreen key='" + Key + "'");

            SwitchToo(Key, ManagerScreenState.Menu);
            return true;
        }
        public void GoMainMenu()
        {
            if (string.IsNullOrEmpty(mainMenuHolder))
                throw new Exception("Firefly Error #004 : MainMenuKey is Null (FATAL)");

            SwitchToo(mainMenuHolder, ManagerScreenState.Menu);
        }

        public object GetCurrentScreen() {
            switch (mScreenState)
            {
                case ManagerScreenState.Game: return gameScreens[screenKeyHolder];
                case ManagerScreenState.Menu: return menuScreens[screenKeyHolder]; 
                default: throw new Exception("Firefly Error #008 : GetCurrentScreen() is used to return a splash");
            }
        }
        public Screenbase GetScreen(string key)
        {
            if (gameScreens.ContainsKey(key))
                return gameScreens[key];
            else if (menuScreens.ContainsKey(key))
                return menuScreens[key];

            throw new Exception("UNKNOWN ERROR #0x001");
        }

        #endregion

        #region @@## Graphics Management ##@@
        private void setupFromConfig()
        {
            if (gdm == null)
                gdm = new GraphicsDeviceManager(Game);

            gdm.PreferredBackBufferWidth = Config.GetValueByFlag<int>("width");
            gdm.PreferredBackBufferHeight = Config.GetValueByFlag<int>("height");
            gdm.IsFullScreen = Config.GetValueByFlag<bool>("fullscreen");
            gdm.GraphicsProfile = (Config.GetValueByFlag<bool>("hidef") ? GraphicsProfile.HiDef : GraphicsProfile.Reach);
            gdm.PreferMultiSampling = Config.GetValueByFlag<bool>("multisampling"); 
            gdm.ApplyChanges();
            _ManagerLog.Write("Settings loaded & applied from config");
        }
        
        public void ChangeGraphics(int width, int height, bool fullscreen)
        {
            gdm.PreferredBackBufferWidth = width;
            gdm.PreferredBackBufferHeight = height;
            gdm.IsFullScreen = fullscreen;
            gdm.ApplyChanges();

            Config.EditFlagValue<int>("width", width);
            Config.EditFlagValue<int>("height", height);
            Config.EditFlagValue<bool>("fullscreen", fullscreen);
            _ManagerLog.Write("Graphics Settings Changed");
        }
        public Rectangle WindowRect
        {
            get { return new Rectangle(0, 0, gdm.PreferredBackBufferWidth, gdm.PreferredBackBufferHeight); }
        }
        public Vector2 WindowRez
        {
            get { return new Vector2(gdm.PreferredBackBufferWidth, gdm.PreferredBackBufferHeight); }
        }
        #endregion

        private void OnGameExiting(object sender, EventArgs e) {
            //save out config file
            Config.SaveConfigFile();

            //Save out debug log streams
        }
                
        public override void Update(GameTime gameTime)
        {
            UpdateScreens(gameTime);
            base.Update(gameTime);
        }
        
        public void RenderScreens(GameTime time) {

            if (mScreenState == ManagerScreenState.Splashes)
                splashScreens[splashCounter].drawScreen(BatchDevice, time);
            else if (mScreenState == ManagerScreenState.Game)
                gameScreens[screenKeyHolder].drawScreen(BatchDevice, time);
            else if (mScreenState == ManagerScreenState.Menu)
                menuScreens[screenKeyHolder].drawScreen(BatchDevice, time);
        }
        public void UpdateScreens(GameTime time) {
            if (mScreenState == ManagerScreenState.Splashes)
            {
                splashScreens[splashCounter].updateScreen(time);
                if (splashScreens[splashCounter].IsFinished) {
                    splashCounter++;
                    if (splashCounter >= splashScreens.Count) {
                        mScreenState = ManagerScreenState.Menu;
                        screenKeyHolder = mainMenuHolder;
                    }
                }
            }
            else if (mScreenState == ManagerScreenState.Menu)
            {
                menuScreens[screenKeyHolder].updateScreen(time);
            }
            else if (mScreenState == ManagerScreenState.Game)
            {
                gameScreens[screenKeyHolder].updateScreen(time);
            }
             
        }

        public void LoadScreenManager() {
            BatchDevice = new SpriteBatch(Game.GraphicsDevice); 

            //Load all spalash screens
            List<Screenbase> sbList = new List<Screenbase>();
            sbList.AddRange(splashScreens);
            sbList.AddRange(menuScreens.Values);
            sbList.AddRange(gameScreens.Values);

            foreach (Screenbase b in sbList)
                b.preLoad(Game.Content);

            _ManagerLog.Write("Number of screens Loaded: " + sbList.Count);
        }
    }
}
